Goal

To create a heatmap of Baltimore city crime data, in an effort to determine where NOT to live.

Approach

I plan to create a google fusion table because it has heatmap functionality built in and I might be able to easily overlay it on the housing map I already have (which really would speed things up).

Here my goal is to prep data for fusion table. It needs geocodes for approximate location (which it already has) and I want to take advantage of the optional weights to better represent the dangers as I personally perceive them.

Import data

dl_date <- Sys.Date()
save_file <- gsub("-", "", dl_date) %>%
    paste0("~/Housing/", ., "-BPD_Part1_crime.csv")
BC_crime_url <- "https://data.baltimorecity.gov/api/views/wsfq-mvij/rows.csv?accessType=DOWNLOAD"
download.file(BC_crime_url, destfile = save_file)
crime <- read_csv(save_file, col_types =
                      cols(
                          CrimeDate = col_date(format = "%m/%d/%Y"),
                          CrimeTime = col_character(), #problems with time format
                          CrimeCode = col_factor(levels = NULL),
                          Location = col_character(),
                          Description = col_factor(levels = NULL),
                          `Inside/Outside` = col_factor(levels = NULL),
                          Weapon = col_factor(levels = NULL),
                          Post = col_integer(),
                          District = col_character(),
                          Neighborhood = col_character(),
                          Longitude = col_double(),
                          Latitude = col_double(),
                          `Location 1` = col_character(),
                          Premise = col_character(),
                          `Total Incidents` = col_integer()
                          )
                  )

problems(crime)

TIME adjustment

Just realized that time doens’t really matter for my purposes. I’m just interested in date… but, hey, no harm in practicing!

time_err_ind <- !grepl(":", crime$CrimeTime)
sum(time_err_ind) # number of errors
[1] 4324
time_err <- crime$CrimeTime[time_err_ind]

examine time errors

head(time_err, 50)
 [1] "1218" "2014" "1929" "1859" "1859" "1840" "0245" "1856" "1834" "1834" "0257"
[12] "1658" "1658" "1658" "1545" "1620" "2100" "2010" "1956" "1326" "2013" "1710"
[23] "1710" "1710" "1706" "1050" "2324" "2208" "1857" "2118" "1316" "2115" "1835"
[34] "1200" "1123" "2126" "1902" "1457" "1312" "1226" "2228" "1601" "2208" "0322"
[45] "0322" "0322" "0211" "0055" "1639" "1639"
# string length of errors
str_len <- nchar(time_err)
table(str_len)
str_len
   3    4    5 
   1 4322    1 
time_err[str_len != 4]
[1] "1826h" "242"  

don’t strip seconds, there are unique values

# any seconds besides '00' in correctly formatted values
sub("(.*)(\\:[0-9]{2}$)", "\\2", crime$CrimeTime) %>%
    unique() %>%
    head(50)
 [1] ":00"  "1218" "2014" "1929" "1859" "1840" "0245" "1856" "1834" ":13"  "0257"
[12] "1658" "1545" "1620" ":09"  ":10"  "2100" "2010" "1956" ":33"  "1326" ":50" 
[23] "2013" "1710" "1706" ":43"  ":11"  "1050" ":49"  "2324" ":22"  ":03"  ":56" 
[34] ":38"  "2208" "1857" ":32"  ":46"  "2118" ":07"  "1316" ":19"  ":54"  "2115"
[45] "1835" "1200" ":31"  "1123" "2126" "1902"

Stripped “h” from 5 length error, added “0” to front of 3 length error and then added “:” to the middle of every time plus “:00:” at end to make time format all the same.

time_adj <- crime$CrimeTime
time_adj <- gsub("h", "", time_adj)
unique(nchar(time_adj))
[1] 8 4 5 3

still has length 5 character?

time_adj[nchar(time_adj) == 5]
[1] "12:27"

it has a colon so it’s fine. I’ll just process all the 4 lengths in 2 steps to capture it

fix 3 length issue

time_adj <- if_else(nchar(time_adj) == 3, paste0("0", time_adj), time_adj)
unique(nchar(time_adj))
[1] 8 4 5

adj all

time_adj <- if_else(nchar(time_adj) == 4,
               sub("([0-9]{2})([0-9]{2})", "\\1:\\2", time_adj),
               time_adj)
unique(nchar(time_adj))
[1] 8 5
time_adj[nchar(time_adj) == 5] %>% head(50) #looks good, add secs
 [1] "12:18" "20:14" "19:29" "18:59" "18:59" "18:40" "02:45" "18:56" "18:34" "18:34"
[11] "02:57" "16:58" "16:58" "16:58" "15:45" "16:20" "21:00" "20:10" "19:56" "13:26"
[21] "20:13" "17:10" "17:10" "17:10" "17:06" "10:50" "23:24" "22:08" "18:57" "21:18"
[31] "13:16" "21:15" "18:35" "12:00" "11:23" "21:26" "19:02" "14:57" "13:12" "12:26"
[41] "22:28" "16:01" "22:08" "03:22" "03:22" "03:22" "02:11" "00:55" "16:39" "16:39"
time_adj <- if_else(nchar(time_adj) == 5,
               paste0(time_adj, ":00"),
               time_adj)
unique(nchar(time_adj))
[1] 8
# reformat as date
crime <- mutate(crime, CrimeTime = parse_time(time_adj, format = "%H:%M:%S"))
head(crime$CrimeTime, 20)
23:55:00
23:37:00
23:00:00
22:25:00
21:56:00
21:50:00
21:30:00
21:15:00
21:03:00
21:00:00
21:00:00
20:36:00
20:30:00
20:24:00
20:15:00
20:00:00
19:14:00
19:00:00
18:51:00
18:50:00

Generate weights

what data is available?

str(crime)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   251676 obs. of  15 variables:
 $ CrimeDate      : Date, format: "2018-03-03" "2018-03-03" ...
 $ CrimeTime      :Classes 'hms', 'difftime'  atomic [1:251676] 86100 85020 82800 80700 78960 ...
  .. ..- attr(*, "units")= chr "secs"
 $ CrimeCode      : Factor w/ 81 levels "6J","4C","4E",..: 1 2 3 3 3 2 4 3 5 6 ...
 $ Location       : chr  "1000 ASHLAND CT" "2800 W NORTH AVE" "1400 E FORT AVE" "800 E JEFFREY ST" ...
 $ Description    : Factor w/ 15 levels "LARCENY","AGG. ASSAULT",..: 1 2 3 3 3 2 4 3 5 2 ...
 $ Inside/Outside : Factor w/ 4 levels "I","O","Outside",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Weapon         : Factor w/ 4 levels "OTHER","HANDS",..: NA 1 2 2 2 1 NA 2 3 2 ...
 $ Post           : int  312 811 943 913 111 415 323 841 721 426 ...
 $ District       : chr  "EASTERN" "SOUTHWESTERN" "SOUTHERN" "SOUTHERN" ...
 $ Neighborhood   : chr  "Oldtown" "Walbrook" "Locust Point" "Brooklyn" ...
 $ Longitude      : num  -76.6 -76.7 -76.6 -76.6 -76.6 ...
 $ Latitude       : num  39.3 39.3 39.3 39.2 39.3 ...
 $ Location 1     : chr  "(39.30019, -76.60352)" "(39.30923, -76.66498)" "(39.26944, -76.59492)" "(39.23236, -76.60041)" ...
 $ Premise        : chr  "ROW/TOWNHO" "ROW/TOWNHO" "BARBER/BEA" "ROW/TOWNHO" ...
 $ Total Incidents: int  1 1 1 1 1 1 1 1 1 1 ...

i don’t know what the crime code means and a quick google search didn’t help me figure it out so I’ll just leave it alone.

The helpful variables will be the ‘Description’ and ‘Weapon’. I don’t know if it being inside or outside would be worse and if it happened in a house or business doesn’t matter so much to me because the proximity to danger is the same, so Premise doesn’t matter. I wonder if there are any Total Incidents greater than 1. That might matter

sum(crime$`Total Incidents` > 1)
[1] 0
sum(crime$`Total Incidents` != 1)
[1] 0

‘Total Incidents’ is always 1 so that’s useless.

So, I’ll want my final weight to be some combination of Description and Weapon adjusted for time (because it’s less relevant if it happened years ago).

Determine Description + Weapon weights

The more dangerous something is to my family the worse it is. So, I’ll weight those higher. I also want to protect my car if possible, preferentially from being stolen completely and then from damage. I’m less worried about stuff being stolen from it because I can leave it somewhat empty (minus the emergency stuff).

levels(crime$Description) %>% sort()
 [1] "AGG. ASSAULT"         "ARSON"                "ASSAULT BY THREAT"   
 [4] "AUTO THEFT"           "BURGLARY"             "COMMON ASSAULT"      
 [7] "HOMICIDE"             "LARCENY"              "LARCENY FROM AUTO"   
[10] "RAPE"                 "ROBBERY - CARJACKING" "ROBBERY - COMMERCIAL"
[13] "ROBBERY - RESIDENCE"  "ROBBERY - STREET"     "SHOOTING"            
count(crime, Description) %>% arrange(desc(n))
levels(crime$Weapon)
[1] "OTHER"   "HANDS"   "FIREARM" "KNIFE"  

I’m not sure what ‘OTHER’ weapon refers to.

count(crime, Description, Weapon) %>%
    spread(key = Weapon, value = n) %>%
    arrange(desc(FIREARM), desc(KNIFE), desc(OTHER))

Based on comparison to firearms and knives it looks like ‘Other’ is weapons worse than ‘Hands’, so I’ll weight them in that order.

If I were to order the type of crime alone it would probably be:

  1. HOMICIDE
  2. AGG. ASSAULT = serious bodily harm, probably includes rape with firearm/knife
  3. RAPE
  4. SHOOTING
  5. ROBBERY - RESIDENCE
    • ROBBERY = use of force
  6. ROBBERY - CARJACKING
  7. ROBBERY - STREET
  8. AUTO THEFT = stolen car (w/o force)
  9. COMMON ASSAULT
  10. ROBBERY - COMMERCIAL
  11. BURGLARY = entering a place illegally w/some illegal intent
  12. ASSAULT BY THREAT = likely only words, never occurs with weapon
  13. ARSON
  14. LARCENY FROM AUTO
    • LARCENY = theft without use of force
  15. LARCENY

Larceny comes with living in a city. We’ll deal with it.

There’s going to be some relatedness between type of weapon and crime. I’ll quickly replace any NA values with the value None and then examine the relatedness of weapon and description using a decision tree and some correlation calcs before I decide on final weights and how to organize them.

sum(is.na(crime$Description))
[1] 0
sum(is.na(crime$Weapon))
[1] 164181
crime <- mutate(crime,
                Weapon = as.factor(
                    if_else(is.na(Weapon), "NONE", as.character(Weapon))
                    )
)
str(crime)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   251676 obs. of  15 variables:
 $ CrimeDate      : Date, format: "2018-03-03" "2018-03-03" ...
 $ CrimeTime      :Classes 'hms', 'difftime'  atomic [1:251676] 86100 85020 82800 80700 78960 ...
  .. ..- attr(*, "units")= chr "secs"
 $ CrimeCode      : Factor w/ 81 levels "6J","4C","4E",..: 1 2 3 3 3 2 4 3 5 6 ...
 $ Location       : chr  "1000 ASHLAND CT" "2800 W NORTH AVE" "1400 E FORT AVE" "800 E JEFFREY ST" ...
 $ Description    : Factor w/ 15 levels "LARCENY","AGG. ASSAULT",..: 1 2 3 3 3 2 4 3 5 2 ...
 $ Inside/Outside : Factor w/ 4 levels "I","O","Outside",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Weapon         : Factor w/ 5 levels "FIREARM","HANDS",..: 4 5 2 2 2 5 4 2 1 2 ...
 $ Post           : int  312 811 943 913 111 415 323 841 721 426 ...
 $ District       : chr  "EASTERN" "SOUTHWESTERN" "SOUTHERN" "SOUTHERN" ...
 $ Neighborhood   : chr  "Oldtown" "Walbrook" "Locust Point" "Brooklyn" ...
 $ Longitude      : num  -76.6 -76.7 -76.6 -76.6 -76.6 ...
 $ Latitude       : num  39.3 39.3 39.3 39.2 39.3 ...
 $ Location 1     : chr  "(39.30019, -76.60352)" "(39.30923, -76.66498)" "(39.26944, -76.59492)" "(39.23236, -76.60041)" ...
 $ Premise        : chr  "ROW/TOWNHO" "ROW/TOWNHO" "BARBER/BEA" "ROW/TOWNHO" ...
 $ Total Incidents: int  1 1 1 1 1 1 1 1 1 1 ...

Description ~ Weapon

# Weapon ~ Description was not what I wanted
crime_tree <- train(Description ~ Weapon, data = crime, method = "rpart")
crime_tree
CART 

251676 samples
     1 predictor
    15 classes: 'LARCENY', 'AGG. ASSAULT', 'COMMON ASSAULT', 'ROBBERY - RESIDENCE', 'ROBBERY - COMMERCIAL', 'ROBBERY - STREET', 'LARCENY FROM AUTO', 'BURGLARY', 'AUTO THEFT', 'ROBBERY - CARJACKING', 'ASSAULT BY THREAT', 'ARSON', 'SHOOTING', 'HOMICIDE', 'RAPE' 

No pre-processing
Resampling: Bootstrapped (25 reps) 
Summary of sample sizes: 251676, 251676, 251676, 251676, 251676, 251676, ... 
Resampling results across tuning parameters:

  cp           Accuracy   Kappa    
  0.005695894  0.4692203  0.3481855
  0.112349226  0.4015323  0.2494283
  0.204077489  0.3128910  0.1278410

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.005695894.
fancyRpartPlot(crime_tree$finalModel)

Shows predicted class, predicted probability of each class, and percentage of observations in each node (but I don’t know the order). Oh well, that turned out to be less useful than I thought since the model only took into account hands and none as weapons.

Let’s calculate the strength of association using Cramér’s V and the vcd package.

assocstats(table(crime$Description, crime$Weapon))
                    X^2 df P(> X^2)
Likelihood Ratio 413153 56        0
Pearson          510717 56        0

Phi-Coefficient   : NA 
Contingency Coeff.: 0.818 
Cramer's V        : 0.712 
chisq.test(crime$Description, crime$Weapon)

    Pearson's Chi-squared test

data:  crime$Description and crime$Weapon
X-squared = 510720, df = 56, p-value < 2.2e-16

Final Description and Weapon weights

For Description I will weight things as follows:

  • 1 = rank 15 to 12 (LARCENY, LARCENY FROM AUTO, ARSON, ASSAULT BY THREAT)
  • 2 = rank 11 and 10 (BURGLARY, ROBBERY - COMMERCIAL)
  • 4.5 = rank 9 to 7 (COMMON ASSAULT, AUTO THEFT, ROBBERY - STREET)
  • 6 = rank 6 and 5 (ROBBERY - CARJACKING, ROBBERY - RESIDENCE)
  • 7 = rank 4 to 1 (SHOOTING, RAPE, AGG. ASSAULT, HOMICIDE)

For Weapon I will weight things as follows:

  • 0 = NONE or HANDS
  • 3 = OTHER
  • 5 = KNIFE
  • 7 = FIREARM

These 2 weights will be added to create a final Crime-Weapon weight and adjusted so that the lowest value is 1 (i.e. has no special weight).

crime_wt <- c(LARCENY = 1, 'LARCENY FROM AUTO' = 1, ARSON = 1,
              'ASSAULT BY THREAT' = 1, BURGLARY = 2, 'ROBBERY - COMMERCIAL' = 2,
              'COMMON ASSAULT' = 4.5, 'AUTO THEFT' = 4.5, 'ROBBERY - STREET' = 4.5,
              'ROBBERY - CARJACKING' = 6, 'ROBBERY - RESIDENCE' = 6, SHOOTING = 7,
              RAPE = 7, 'AGG. ASSAULT' = 7, HOMICIDE = 7)
crime_wt <- as.data.frame(crime_wt) %>%
    rownames_to_column(var = "Description")
wep_wt <- c(NONE = 0, HANDS = 0, OTHER = 3, KNIFE = 5, FIREARM = 7)
wep_wt <- as.data.frame(wep_wt) %>%
    rownames_to_column(var = "Weapon")
crime <- left_join(crime, crime_wt, by = "Description") %>%
    left_join(wep_wt, by = "Weapon")
Column `Description` joining factor and character vector, coercing into character vectorColumn `Weapon` joining factor and character vector, coercing into character vector
str(crime)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   251676 obs. of  17 variables:
 $ CrimeDate      : Date, format: "2018-03-03" "2018-03-03" ...
 $ CrimeTime      :Classes 'hms', 'difftime'  atomic [1:251676] 86100 85020 82800 80700 78960 ...
  .. ..- attr(*, "units")= chr "secs"
 $ CrimeCode      : Factor w/ 81 levels "6J","4C","4E",..: 1 2 3 3 3 2 4 3 5 6 ...
 $ Location       : chr  "1000 ASHLAND CT" "2800 W NORTH AVE" "1400 E FORT AVE" "800 E JEFFREY ST" ...
 $ Description    : chr  "LARCENY" "AGG. ASSAULT" "COMMON ASSAULT" "COMMON ASSAULT" ...
 $ Inside/Outside : Factor w/ 4 levels "I","O","Outside",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Weapon         : chr  "NONE" "OTHER" "HANDS" "HANDS" ...
 $ Post           : int  312 811 943 913 111 415 323 841 721 426 ...
 $ District       : chr  "EASTERN" "SOUTHWESTERN" "SOUTHERN" "SOUTHERN" ...
 $ Neighborhood   : chr  "Oldtown" "Walbrook" "Locust Point" "Brooklyn" ...
 $ Longitude      : num  -76.6 -76.7 -76.6 -76.6 -76.6 ...
 $ Latitude       : num  39.3 39.3 39.3 39.2 39.3 ...
 $ Location 1     : chr  "(39.30019, -76.60352)" "(39.30923, -76.66498)" "(39.26944, -76.59492)" "(39.23236, -76.60041)" ...
 $ Premise        : chr  "ROW/TOWNHO" "ROW/TOWNHO" "BARBER/BEA" "ROW/TOWNHO" ...
 $ Total Incidents: int  1 1 1 1 1 1 1 1 1 1 ...
 $ crime_wt       : num  1 7 4.5 4.5 4.5 7 6 4.5 2 7 ...
 $ wep_wt         : num  0 3 0 0 0 3 0 0 7 0 ...

Weight adjusted by time

Events that happened years ago should be weighted less. I need to determine how many events happen in a year to have some idea as to how to weight them.

tmp <- tibble(year = year(crime$CrimeDate), month = month(crime$CrimeDate))
# crimes/year
table(tmp$year)

 2013  2014  2015  2016  2017  2018 
49568 45973 48858 48847 51753  6677 
# mean(crimes)/month
table(tmp$year, tmp$month) %>%
    .[. > 500] %>%  # removes current and future months (with low counts)
    mean()
[1] 4055.839

That’s a lot of crime in a month and year. Let’s see, I guess the best way to do it is to select a period that will not be down-weighted and then gradually down-weight by month (I may have to iterate on this). In the last size months there would have been about 2.43310^{4} crimes. The area of Baltimore City is about 92.1 sq miles. So that’s about 264 crimes per square mile which doesn’t seem too heavy.

I’M NOT TAKING THIS APPROACH

So anything within the last 6 months will be our \(final\_wt * 1\). There are 63 months in the data set so we’ll just develop a sequence from this month to the last headed toward zero.

Gradually downweight starting from this month backward

# split into 63 periods starting at today
t <- map_dbl(0:63, ~today() %m-% months(.x)) %>% as_date()
# oldest date between last 2 values?
nth(t, -2) > min(crime$CrimeDate) & min(crime$CrimeDate) > last(t)
[1] TRUE
# weights by month
month_wt <- seq(1, 0, along.with = t)

now add this to crime

# SET time_wt as double with length = crime observations
t_wt <- numeric(nrow(crime))
# FOR each month division, m
#    IF date is < upper month date AND date >= lower month date THEN
#        SET weight equal to weight for this division
#    ELSE
#        DO nothing
#END FOR
for (n in seq_along(t)) {
    t_wt <- if_else(
        crime$CrimeDate < t[n] & crime$CrimeDate >= t[n + 1],
        month_wt[n],
        t_wt
        )
}
# verify correct
unique(t_wt)
 [1] 1.00000000 0.96825397 0.87301587 0.80952381 0.77777778 0.98412698 0.95238095
 [8] 0.93650794 0.92063492 0.90476190 0.88888889 0.85714286 0.84126984 0.82539683
[15] 0.79365079 0.76190476 0.74603175 0.73015873 0.71428571 0.69841270 0.68253968
[22] 0.66666667 0.65079365 0.63492063 0.61904762 0.60317460 0.58730159 0.57142857
[29] 0.55555556 0.53968254 0.52380952 0.19047619 0.17460317 0.50793651 0.49206349
[36] 0.47619048 0.46031746 0.44444444 0.42857143 0.41269841 0.39682540 0.38095238
[43] 0.36507937 0.34920635 0.33333333 0.31746032 0.30158730 0.28571429 0.26984127
[50] 0.25396825 0.23809524 0.22222222 0.20634921 0.15873016 0.14285714 0.12698413
[57] 0.11111111 0.09523810 0.07936508 0.06349206 0.04761905 0.03174603 0.01587302
length(t_wt) == nrow(crime)
[1] TRUE
crime <- mutate(crime,
                time_wt = t_wt,
                overall_wt = (crime_wt + wep_wt) * time_wt)
# verify no zero vals
range(crime$overall_wt)
[1]  0.01587302 14.00000000
str(crime)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   251676 obs. of  19 variables:
 $ CrimeDate      : Date, format: "2018-03-03" "2018-03-03" ...
 $ CrimeTime      :Classes 'hms', 'difftime'  atomic [1:251676] 86100 85020 82800 80700 78960 ...
  .. ..- attr(*, "units")= chr "secs"
 $ CrimeCode      : Factor w/ 81 levels "6J","4C","4E",..: 1 2 3 3 3 2 4 3 5 6 ...
 $ Location       : chr  "1000 ASHLAND CT" "2800 W NORTH AVE" "1400 E FORT AVE" "800 E JEFFREY ST" ...
 $ Description    : chr  "LARCENY" "AGG. ASSAULT" "COMMON ASSAULT" "COMMON ASSAULT" ...
 $ Inside/Outside : Factor w/ 4 levels "I","O","Outside",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Weapon         : chr  "NONE" "OTHER" "HANDS" "HANDS" ...
 $ Post           : int  312 811 943 913 111 415 323 841 721 426 ...
 $ District       : chr  "EASTERN" "SOUTHWESTERN" "SOUTHERN" "SOUTHERN" ...
 $ Neighborhood   : chr  "Oldtown" "Walbrook" "Locust Point" "Brooklyn" ...
 $ Longitude      : num  -76.6 -76.7 -76.6 -76.6 -76.6 ...
 $ Latitude       : num  39.3 39.3 39.3 39.2 39.3 ...
 $ Location 1     : chr  "(39.30019, -76.60352)" "(39.30923, -76.66498)" "(39.26944, -76.59492)" "(39.23236, -76.60041)" ...
 $ Premise        : chr  "ROW/TOWNHO" "ROW/TOWNHO" "BARBER/BEA" "ROW/TOWNHO" ...
 $ Total Incidents: int  1 1 1 1 1 1 1 1 1 1 ...
 $ crime_wt       : num  1 7 4.5 4.5 4.5 7 6 4.5 2 7 ...
 $ wep_wt         : num  0 3 0 0 0 3 0 0 7 0 ...
 $ time_wt        : num  1 1 1 1 1 1 1 1 1 1 ...
 $ overall_wt     : num  1 10 4.5 4.5 4.5 10 6 4.5 9 7 ...

Write to csv for loading to google fusion tables

write_csv(crime, path = paste0(sub(".csv$", "", save_file), "-WEIGHTED.csv"))

Attempted Google Fusion Table

After loading the data into a new fusion table, I set the location to use latitude and longitude, chose heat map, set the weights to overall_wt, and attempted various positions for the “radius”. The resulting map was unexpectedly sparse.

Baltimore City Crime Map

Baltimore City Crime Map

It turns out the fusion table map is using the MAPS API Heatmap layer which cannot handle mapping more than 1,000 rows. No wonder the data was sparse!

At this point I realized the map wasn’t going to be what I hoped. But, out of curiosity, I adjusted the map by filtering the data. I was able to reduce the number of rows to 998 by selecting only the data occurring between 2018-01-01 and 2018-03-10 and setting a filter to include only the top 6 worse crimes (in my opinion: Homicide, Agg. Assault, Rape, Shooting, Robbery - Carjacking, and Robbery - Residence).

Baltimore City Crime Map - Filtered

Baltimore City Crime Map - Filtered

Sheesh! Even with only about 2 months and 6 types of crime Baltimore City at this level looks like a pretty dangerous place (except maybe the Roland Park area).

If I zoom in further to either of these maps to look at the data on a neighborhood level the individual values become too sparse to be useful. At this zoom level, the safer looking areas probably look safer because of missing data, more so than actually being safer.

If the whole point of this is to identify safer areas for a family to live in. I’m going to need a new approach!

LS0tCnRpdGxlOiAiQmFsdGltb3JlIENyaW1lIEFuYWx5c2lzIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKLS0tCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShjYXJldCkKbGlicmFyeShyYXR0bGUpCmxpYnJhcnkodmNkKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ21hcCkKbGlicmFyeShydmVzdCkKYGBgCgoKIyBHb2FsClRvIGNyZWF0ZSBhIGhlYXRtYXAgb2YgQmFsdGltb3JlIGNpdHkgY3JpbWUgZGF0YSwgaW4gYW4gZWZmb3J0IHRvIGRldGVybWluZSB3aGVyZSBfTk9UXyB0byBsaXZlLgoKIyBBcHByb2FjaApJIHBsYW4gdG8gY3JlYXRlIGEgZ29vZ2xlIGZ1c2lvbiB0YWJsZSBiZWNhdXNlIGl0IGhhcyBoZWF0bWFwIGZ1bmN0aW9uYWxpdHkgYnVpbHQgaW4gYW5kIEkgbWlnaHQgYmUgYWJsZSB0byBlYXNpbHkgb3ZlcmxheSBpdCBvbiB0aGUgaG91c2luZyBtYXAgSSBhbHJlYWR5IGhhdmUgKHdoaWNoIHJlYWxseSB3b3VsZCBzcGVlZCB0aGluZ3MgdXApLgoKSGVyZSBteSBnb2FsIGlzIHRvIHByZXAgZGF0YSBmb3IgZnVzaW9uIHRhYmxlLiBJdCBuZWVkcyBnZW9jb2RlcyBmb3IgYXBwcm94aW1hdGUgbG9jYXRpb24gKHdoaWNoIGl0IGFscmVhZHkgaGFzKSBhbmQgSSB3YW50IHRvIHRha2UgYWR2YW50YWdlIG9mIHRoZSBvcHRpb25hbCB3ZWlnaHRzIHRvIGJldHRlciByZXByZXNlbnQgdGhlIGRhbmdlcnMgYXMgSSBwZXJzb25hbGx5IHBlcmNlaXZlIHRoZW0uCgojIEltcG9ydCBkYXRhCmBgYHtyIGRvd25sb2FkfQpkbF9kYXRlIDwtIFN5cy5EYXRlKCkKCnNhdmVfZmlsZSA8LSBnc3ViKCItIiwgIiIsIGRsX2RhdGUpICU+JQogICAgcGFzdGUwKCJ+L0hvdXNpbmcvIiwgLiwgIi1CUERfUGFydDFfY3JpbWUuY3N2IikKQkNfY3JpbWVfdXJsIDwtICJodHRwczovL2RhdGEuYmFsdGltb3JlY2l0eS5nb3YvYXBpL3ZpZXdzL3dzZnEtbXZpai9yb3dzLmNzdj9hY2Nlc3NUeXBlPURPV05MT0FEIgpgYGAKCmBgYHtyfQpkb3dubG9hZC5maWxlKEJDX2NyaW1lX3VybCwgZGVzdGZpbGUgPSBzYXZlX2ZpbGUpCmBgYAoKYGBge3IgcmVhZH0KY3JpbWUgPC0gcmVhZF9jc3Yoc2F2ZV9maWxlLCBjb2xfdHlwZXMgPQogICAgICAgICAgICAgICAgICAgICAgY29scygKICAgICAgICAgICAgICAgICAgICAgICAgICBDcmltZURhdGUgPSBjb2xfZGF0ZShmb3JtYXQgPSAiJW0vJWQvJVkiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBDcmltZVRpbWUgPSBjb2xfY2hhcmFjdGVyKCksICNwcm9ibGVtcyB3aXRoIHRpbWUgZm9ybWF0CiAgICAgICAgICAgICAgICAgICAgICAgICAgQ3JpbWVDb2RlID0gY29sX2ZhY3RvcihsZXZlbHMgPSBOVUxMKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBMb2NhdGlvbiA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBEZXNjcmlwdGlvbiA9IGNvbF9mYWN0b3IobGV2ZWxzID0gTlVMTCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYEluc2lkZS9PdXRzaWRlYCA9IGNvbF9mYWN0b3IobGV2ZWxzID0gTlVMTCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgV2VhcG9uID0gY29sX2ZhY3RvcihsZXZlbHMgPSBOVUxMKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBQb3N0ID0gY29sX2ludGVnZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBEaXN0cmljdCA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBOZWlnaGJvcmhvb2QgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgTG9uZ2l0dWRlID0gY29sX2RvdWJsZSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgIExhdGl0dWRlID0gY29sX2RvdWJsZSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGBMb2NhdGlvbiAxYCA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVtaXNlID0gY29sX2NoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGBUb3RhbCBJbmNpZGVudHNgID0gY29sX2ludGVnZXIoKQogICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKQoKcHJvYmxlbXMoY3JpbWUpCmBgYAoKCiMjIFRJTUUgYWRqdXN0bWVudApKdXN0IHJlYWxpemVkIHRoYXQgdGltZSBkb2Vucyd0IHJlYWxseSBtYXR0ZXIgZm9yIG15IHB1cnBvc2VzLiBJJ20ganVzdCBpbnRlcmVzdGVkIGluIGRhdGUuLi4gYnV0LCBoZXksIG5vIGhhcm0gaW4gcHJhY3RpY2luZyEKCmBgYHtyIHRpbWVfZXJyb3JzfQp0aW1lX2Vycl9pbmQgPC0gIWdyZXBsKCI6IiwgY3JpbWUkQ3JpbWVUaW1lKQpzdW0odGltZV9lcnJfaW5kKSAjIG51bWJlciBvZiBlcnJvcnMKCnRpbWVfZXJyIDwtIGNyaW1lJENyaW1lVGltZVt0aW1lX2Vycl9pbmRdCmBgYAoKZXhhbWluZSB0aW1lIGVycm9ycwoKYGBge3IgZXJyX3N0cl9sZW59CmhlYWQodGltZV9lcnIsIDUwKQoKIyBzdHJpbmcgbGVuZ3RoIG9mIGVycm9ycwpzdHJfbGVuIDwtIG5jaGFyKHRpbWVfZXJyKQp0YWJsZShzdHJfbGVuKQoKdGltZV9lcnJbc3RyX2xlbiAhPSA0XQpgYGAKCmRvbid0IHN0cmlwIHNlY29uZHMsIHRoZXJlIGFyZSB1bmlxdWUgdmFsdWVzCmBgYHtyfQojIGFueSBzZWNvbmRzIGJlc2lkZXMgJzAwJyBpbiBjb3JyZWN0bHkgZm9ybWF0dGVkIHZhbHVlcwpzdWIoIiguKikoXFw6WzAtOV17Mn0kKSIsICJcXDIiLCBjcmltZSRDcmltZVRpbWUpICU+JQogICAgdW5pcXVlKCkgJT4lCiAgICBoZWFkKDUwKQpgYGAKClN0cmlwcGVkICJoIiBmcm9tIDUgbGVuZ3RoIGVycm9yLCBhZGRlZCAiMCIgdG8gZnJvbnQgb2YgMyBsZW5ndGggZXJyb3IgYW5kIHRoZW4gYWRkZWQgIjoiIHRvIHRoZSBtaWRkbGUgb2YgZXZlcnkgdGltZSBwbHVzICI6MDA6IiBhdCBlbmQgdG8gbWFrZSB0aW1lIGZvcm1hdCBhbGwgdGhlIHNhbWUuCgpgYGB7cn0KdGltZV9hZGogPC0gY3JpbWUkQ3JpbWVUaW1lCnRpbWVfYWRqIDwtIGdzdWIoImgiLCAiIiwgdGltZV9hZGopCnVuaXF1ZShuY2hhcih0aW1lX2FkaikpCmBgYApzdGlsbCBoYXMgbGVuZ3RoIDUgY2hhcmFjdGVyPwoKYGBge3J9CnRpbWVfYWRqW25jaGFyKHRpbWVfYWRqKSA9PSA1XQpgYGAKaXQgaGFzIGEgY29sb24gc28gaXQncyBmaW5lLiBJJ2xsIGp1c3QgcHJvY2VzcyBhbGwgdGhlIDQgbGVuZ3RocyBpbiAyIHN0ZXBzIHRvIGNhcHR1cmUgaXQKCmZpeCAzIGxlbmd0aCBpc3N1ZQpgYGB7cn0KdGltZV9hZGogPC0gaWZfZWxzZShuY2hhcih0aW1lX2FkaikgPT0gMywgcGFzdGUwKCIwIiwgdGltZV9hZGopLCB0aW1lX2FkaikKdW5pcXVlKG5jaGFyKHRpbWVfYWRqKSkKYGBgCgphZGogYWxsCmBgYHtyfQp0aW1lX2FkaiA8LSBpZl9lbHNlKG5jaGFyKHRpbWVfYWRqKSA9PSA0LAogICAgICAgICAgICAgICBzdWIoIihbMC05XXsyfSkoWzAtOV17Mn0pIiwgIlxcMTpcXDIiLCB0aW1lX2FkaiksCiAgICAgICAgICAgICAgIHRpbWVfYWRqKQp1bmlxdWUobmNoYXIodGltZV9hZGopKQp0aW1lX2FkaltuY2hhcih0aW1lX2FkaikgPT0gNV0gJT4lIGhlYWQoNTApICNsb29rcyBnb29kLCBhZGQgc2VjcwoKdGltZV9hZGogPC0gaWZfZWxzZShuY2hhcih0aW1lX2FkaikgPT0gNSwKICAgICAgICAgICAgICAgcGFzdGUwKHRpbWVfYWRqLCAiOjAwIiksCiAgICAgICAgICAgICAgIHRpbWVfYWRqKQp1bmlxdWUobmNoYXIodGltZV9hZGopKQoKIyByZWZvcm1hdCBhcyBkYXRlCmNyaW1lIDwtIG11dGF0ZShjcmltZSwgQ3JpbWVUaW1lID0gcGFyc2VfdGltZSh0aW1lX2FkaiwgZm9ybWF0ID0gIiVIOiVNOiVTIikpCmhlYWQoY3JpbWUkQ3JpbWVUaW1lLCAyMCkKYGBgCgojIEdlbmVyYXRlIHdlaWdodHMKCndoYXQgZGF0YSBpcyBhdmFpbGFibGU/CmBgYHtyfQpzdHIoY3JpbWUpCmBgYAoKaSBkb24ndCBrbm93IHdoYXQgdGhlIGNyaW1lIGNvZGUgbWVhbnMgYW5kIGEgcXVpY2sgW2dvb2dsZSBzZWFyY2hdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vc2VhcmNoP3E9YmFsdGltb3JlK2NpdHkrY3JpbWUrY29kZSZybHo9MUMxQ0hCRl9lblVTNzUyVVM3NTImb3E9YmFsdGltb3JlK2NpdHkrY3JpbWUrY29kZSZhcXM9Y2hyb21lLi42OWk1N2o2OWk2NWowajY5aTY0LjMyNjlqMGo3JnNvdXJjZWlkPWNocm9tZSZpZT1VVEYtOCkgZGlkbid0IGhlbHAgbWUgZmlndXJlIGl0IG91dCBzbyBJJ2xsIGp1c3QgbGVhdmUgaXQgYWxvbmUuCgpUaGUgaGVscGZ1bCB2YXJpYWJsZXMgd2lsbCBiZSB0aGUgJ0Rlc2NyaXB0aW9uJyBhbmQgJ1dlYXBvbicuIEkgZG9uJ3Qga25vdyBpZiBpdCBiZWluZyBpbnNpZGUgb3Igb3V0c2lkZSB3b3VsZCBiZSB3b3JzZSBhbmQgaWYgaXQgaGFwcGVuZWQgaW4gYSBob3VzZSBvciBidXNpbmVzcyBkb2Vzbid0IG1hdHRlciBzbyBtdWNoIHRvIG1lIGJlY2F1c2UgdGhlIHByb3hpbWl0eSB0byBkYW5nZXIgaXMgdGhlIHNhbWUsIHNvIGBQcmVtaXNlYCBkb2Vzbid0IG1hdHRlci4gSSB3b25kZXIgaWYgdGhlcmUgYXJlIGFueSBgVG90YWwgSW5jaWRlbnRzYCBncmVhdGVyIHRoYW4gMS4gVGhhdCBtaWdodCBtYXR0ZXIKCmBgYHtyfQpzdW0oY3JpbWUkYFRvdGFsIEluY2lkZW50c2AgPiAxKQpzdW0oY3JpbWUkYFRvdGFsIEluY2lkZW50c2AgIT0gMSkKYGBgCgonVG90YWwgSW5jaWRlbnRzJyBpcyBhbHdheXMgMSBzbyB0aGF0J3MgdXNlbGVzcy4KClNvLCBJJ2xsIHdhbnQgbXkgZmluYWwgd2VpZ2h0IHRvIGJlIHNvbWUgY29tYmluYXRpb24gb2YgYERlc2NyaXB0aW9uYCBhbmQgYFdlYXBvbmAgYWRqdXN0ZWQgZm9yIHRpbWUgKGJlY2F1c2UgaXQncyBsZXNzIHJlbGV2YW50IGlmIGl0IGhhcHBlbmVkIHllYXJzIGFnbykuCgoKIyMgRGV0ZXJtaW5lIERlc2NyaXB0aW9uICsgV2VhcG9uIHdlaWdodHMKClRoZSBtb3JlIGRhbmdlcm91cyBzb21ldGhpbmcgaXMgdG8gbXkgZmFtaWx5IHRoZSB3b3JzZSBpdCBpcy4gU28sIEknbGwgd2VpZ2h0IHRob3NlIGhpZ2hlci4gSSBhbHNvIHdhbnQgdG8gcHJvdGVjdCBteSBjYXIgaWYgcG9zc2libGUsIHByZWZlcmVudGlhbGx5IGZyb20gYmVpbmcgc3RvbGVuIGNvbXBsZXRlbHkgYW5kIHRoZW4gZnJvbSBkYW1hZ2UuIEknbSBsZXNzIHdvcnJpZWQgYWJvdXQgc3R1ZmYgYmVpbmcgc3RvbGVuIGZyb20gaXQgYmVjYXVzZSBJIGNhbiBsZWF2ZSBpdCBzb21ld2hhdCBlbXB0eSAobWludXMgdGhlIGVtZXJnZW5jeSBzdHVmZikuCgpgYGB7cn0KbGV2ZWxzKGNyaW1lJERlc2NyaXB0aW9uKSAlPiUgc29ydCgpCmNvdW50KGNyaW1lLCBEZXNjcmlwdGlvbikgJT4lIGFycmFuZ2UoZGVzYyhuKSkKbGV2ZWxzKGNyaW1lJFdlYXBvbikKYGBgCgpJJ20gbm90IHN1cmUgd2hhdCAnT1RIRVInIHdlYXBvbiByZWZlcnMgdG8uCmBgYHtyfQpjb3VudChjcmltZSwgRGVzY3JpcHRpb24sIFdlYXBvbikgJT4lCiAgICBzcHJlYWQoa2V5ID0gV2VhcG9uLCB2YWx1ZSA9IG4pICU+JQogICAgYXJyYW5nZShkZXNjKEZJUkVBUk0pLCBkZXNjKEtOSUZFKSwgZGVzYyhPVEhFUikpCmBgYAoKQmFzZWQgb24gY29tcGFyaXNvbiB0byBmaXJlYXJtcyBhbmQga25pdmVzIGl0IGxvb2tzIGxpa2UgJ090aGVyJyBpcyB3ZWFwb25zIHdvcnNlIHRoYW4gJ0hhbmRzJywgc28gSSdsbCB3ZWlnaHQgdGhlbSBpbiB0aGF0IG9yZGVyLgoKSWYgSSB3ZXJlIHRvIG9yZGVyIHRoZSB0eXBlIG9mIGNyaW1lIGFsb25lIGl0IHdvdWxkIHByb2JhYmx5IGJlOgoKMS4gSE9NSUNJREUKMi4gKipBR0cuIEFTU0FVTFQqKiA9IHNlcmlvdXMgYm9kaWx5IGhhcm0sIHByb2JhYmx5IGluY2x1ZGVzIHJhcGUgd2l0aCBmaXJlYXJtL2tuaWZlCjMuIFJBUEUKNC4gU0hPT1RJTkcKNS4gUk9CQkVSWSAtIFJFU0lERU5DRQogICAgLSAqKlJPQkJFUlkqKiA9IHVzZSBvZiBmb3JjZQo2LiBST0JCRVJZIC0gQ0FSSkFDS0lORwo3LiBST0JCRVJZIC0gU1RSRUVUCjguICoqQVVUTyBUSEVGVCoqID0gc3RvbGVuIGNhciAody9vIGZvcmNlKQo5LiBDT01NT04gQVNTQVVMVAoxMC4gUk9CQkVSWSAtIENPTU1FUkNJQUwKMTEuICoqQlVSR0xBUlkqKiA9IGVudGVyaW5nIGEgcGxhY2UgaWxsZWdhbGx5IHcvc29tZSBpbGxlZ2FsIGludGVudAoxMi4gKipBU1NBVUxUIEJZIFRIUkVBVCoqID0gbGlrZWx5IG9ubHkgd29yZHMsIG5ldmVyIG9jY3VycyB3aXRoIHdlYXBvbgoxMy4gQVJTT04KMTQuIExBUkNFTlkgRlJPTSBBVVRPCiAgICAtICoqTEFSQ0VOWSoqID0gdGhlZnQgd2l0aG91dCB1c2Ugb2YgZm9yY2UKMTUuIExBUkNFTlkKCkxhcmNlbnkgY29tZXMgd2l0aCBsaXZpbmcgaW4gYSBjaXR5LiBXZSdsbCBkZWFsIHdpdGggaXQuCgpUaGVyZSdzIGdvaW5nIHRvIGJlIHNvbWUgcmVsYXRlZG5lc3MgYmV0d2VlbiB0eXBlIG9mIHdlYXBvbiBhbmQgY3JpbWUuIEknbGwgcXVpY2tseSByZXBsYWNlIGFueSBgTkFgIHZhbHVlcyB3aXRoIHRoZSB2YWx1ZSBgTm9uZWAgYW5kIHRoZW4gZXhhbWluZSB0aGUgcmVsYXRlZG5lc3Mgb2Ygd2VhcG9uIGFuZCBkZXNjcmlwdGlvbiB1c2luZyBhIGRlY2lzaW9uIHRyZWUgYW5kIHNvbWUgY29ycmVsYXRpb24gY2FsY3MgYmVmb3JlIEkgZGVjaWRlIG9uIGZpbmFsIHdlaWdodHMgYW5kIGhvdyB0byBvcmdhbml6ZSB0aGVtLgpgYGB7cn0Kc3VtKGlzLm5hKGNyaW1lJERlc2NyaXB0aW9uKSkKc3VtKGlzLm5hKGNyaW1lJFdlYXBvbikpCgpjcmltZSA8LSBtdXRhdGUoY3JpbWUsCiAgICAgICAgICAgICAgICBXZWFwb24gPSBhcy5mYWN0b3IoCiAgICAgICAgICAgICAgICAgICAgaWZfZWxzZShpcy5uYShXZWFwb24pLCAiTk9ORSIsIGFzLmNoYXJhY3RlcihXZWFwb24pKQogICAgICAgICAgICAgICAgICAgICkKKQpzdHIoY3JpbWUpCmBgYAojIyMgRGVzY3JpcHRpb24gfiBXZWFwb24KYGBge3J9CiMgV2VhcG9uIH4gRGVzY3JpcHRpb24gd2FzIG5vdCB3aGF0IEkgd2FudGVkCmNyaW1lX3RyZWUgPC0gdHJhaW4oRGVzY3JpcHRpb24gfiBXZWFwb24sIGRhdGEgPSBjcmltZSwgbWV0aG9kID0gInJwYXJ0IikKY3JpbWVfdHJlZQpmYW5jeVJwYXJ0UGxvdChjcmltZV90cmVlJGZpbmFsTW9kZWwpCmBgYAoKU2hvd3MgcHJlZGljdGVkIGNsYXNzLCBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgZWFjaCBjbGFzcywgYW5kIHBlcmNlbnRhZ2Ugb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggbm9kZSAoYnV0IEkgZG9uJ3Qga25vdyB0aGUgb3JkZXIpLiBPaCB3ZWxsLCB0aGF0IHR1cm5lZCBvdXQgdG8gYmUgbGVzcyB1c2VmdWwgdGhhbiBJIHRob3VnaHQgc2luY2UgdGhlIG1vZGVsIG9ubHkgdG9vayBpbnRvIGFjY291bnQgaGFuZHMgYW5kIG5vbmUgYXMgd2VhcG9ucy4KCkxldCdzIGNhbGN1bGF0ZSB0aGUgW3N0cmVuZ3RoIG9mIGFzc29jaWF0aW9uXShodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8xMDgwMDcvY29ycmVsYXRpb25zLXdpdGgtdW5vcmRlcmVkLWNhdGVnb3JpY2FsLXZhcmlhYmxlcykgdXNpbmcgW0NyYW3pcidzIFZdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NyYW0lQzMlQTlyJTI3c19WKSBhbmQgdGhlIFt2Y2RdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy92Y2QvdmVyc2lvbnMvMS40LTQvdG9waWNzL2Fzc29jc3RhdHMpIHBhY2thZ2UuCmBgYHtyfQphc3NvY3N0YXRzKHRhYmxlKGNyaW1lJERlc2NyaXB0aW9uLCBjcmltZSRXZWFwb24pKQpgYGAKCmBgYHtyfQpjaGlzcS50ZXN0KGNyaW1lJERlc2NyaXB0aW9uLCBjcmltZSRXZWFwb24pCmBgYAoKIyMjIEZpbmFsIGBEZXNjcmlwdGlvbmAgYW5kIGBXZWFwb25gIHdlaWdodHMKCkZvciBgRGVzY3JpcHRpb25gIEkgd2lsbCB3ZWlnaHQgdGhpbmdzIGFzIGZvbGxvd3M6CgotIDEgPSByYW5rIDE1IHRvIDEyIChMQVJDRU5ZLCBMQVJDRU5ZIEZST00gQVVUTywgQVJTT04sIEFTU0FVTFQgQlkgVEhSRUFUKQotIDIgPSByYW5rIDExIGFuZCAxMCAoQlVSR0xBUlksIFJPQkJFUlkgLSBDT01NRVJDSUFMKQotIDQuNSA9IHJhbmsgOSB0byA3IChDT01NT04gQVNTQVVMVCwgQVVUTyBUSEVGVCwgUk9CQkVSWSAtIFNUUkVFVCkKLSA2ID0gcmFuayA2IGFuZCA1IChST0JCRVJZIC0gQ0FSSkFDS0lORywgUk9CQkVSWSAtIFJFU0lERU5DRSkKLSA3ID0gcmFuayA0IHRvIDEgKFNIT09USU5HLCBSQVBFLCBBR0cuIEFTU0FVTFQsIEhPTUlDSURFKQoKRm9yIGBXZWFwb25gIEkgd2lsbCB3ZWlnaHQgdGhpbmdzIGFzIGZvbGxvd3M6CgotIDAgPSBOT05FIG9yIEhBTkRTCi0gMyA9IE9USEVSCi0gNSA9IEtOSUZFCi0gNyA9IEZJUkVBUk0KClRoZXNlIDIgd2VpZ2h0cyB3aWxsIGJlIGFkZGVkIHRvIGNyZWF0ZSBhIGZpbmFsIENyaW1lLVdlYXBvbiB3ZWlnaHQgYW5kIGFkanVzdGVkIHNvIHRoYXQgdGhlIGxvd2VzdCB2YWx1ZSBpcyAxIChfaS5lLl8gaGFzIG5vIHNwZWNpYWwgd2VpZ2h0KS4KCmBgYHtyfQpjcmltZV93dCA8LSBjKExBUkNFTlkgPSAxLCAnTEFSQ0VOWSBGUk9NIEFVVE8nID0gMSwgQVJTT04gPSAxLAogICAgICAgICAgICAgICdBU1NBVUxUIEJZIFRIUkVBVCcgPSAxLCBCVVJHTEFSWSA9IDIsICdST0JCRVJZIC0gQ09NTUVSQ0lBTCcgPSAyLAogICAgICAgICAgICAgICdDT01NT04gQVNTQVVMVCcgPSA0LjUsICdBVVRPIFRIRUZUJyA9IDQuNSwgJ1JPQkJFUlkgLSBTVFJFRVQnID0gNC41LAogICAgICAgICAgICAgICdST0JCRVJZIC0gQ0FSSkFDS0lORycgPSA2LCAnUk9CQkVSWSAtIFJFU0lERU5DRScgPSA2LCBTSE9PVElORyA9IDcsCiAgICAgICAgICAgICAgUkFQRSA9IDcsICdBR0cuIEFTU0FVTFQnID0gNywgSE9NSUNJREUgPSA3KQpjcmltZV93dCA8LSBhcy5kYXRhLmZyYW1lKGNyaW1lX3d0KSAlPiUKICAgIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiRGVzY3JpcHRpb24iKQoKd2VwX3d0IDwtIGMoTk9ORSA9IDAsIEhBTkRTID0gMCwgT1RIRVIgPSAzLCBLTklGRSA9IDUsIEZJUkVBUk0gPSA3KQp3ZXBfd3QgPC0gYXMuZGF0YS5mcmFtZSh3ZXBfd3QpICU+JQogICAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJXZWFwb24iKQoKY3JpbWUgPC0gbGVmdF9qb2luKGNyaW1lLCBjcmltZV93dCwgYnkgPSAiRGVzY3JpcHRpb24iKSAlPiUKICAgIGxlZnRfam9pbih3ZXBfd3QsIGJ5ID0gIldlYXBvbiIpCnN0cihjcmltZSkKYGBgCgojIyBXZWlnaHQgYWRqdXN0ZWQgYnkgdGltZQoKRXZlbnRzIHRoYXQgaGFwcGVuZWQgeWVhcnMgYWdvIHNob3VsZCBiZSB3ZWlnaHRlZCBsZXNzLiBJIG5lZWQgdG8gZGV0ZXJtaW5lIGhvdyBtYW55IGV2ZW50cyBoYXBwZW4gaW4gYSB5ZWFyIHRvIGhhdmUgc29tZSBpZGVhIGFzIHRvIGhvdyB0byB3ZWlnaHQgdGhlbS4KCmBgYHtyfQp0bXAgPC0gdGliYmxlKHllYXIgPSB5ZWFyKGNyaW1lJENyaW1lRGF0ZSksIG1vbnRoID0gbW9udGgoY3JpbWUkQ3JpbWVEYXRlKSkKCiMgY3JpbWVzL3llYXIKdGFibGUodG1wJHllYXIpCgojIG1lYW4oY3JpbWVzKS9tb250aAp0YWJsZSh0bXAkeWVhciwgdG1wJG1vbnRoKSAlPiUKICAgIC5bLiA+IDUwMF0gJT4lICAjIHJlbW92ZXMgY3VycmVudCBhbmQgZnV0dXJlIG1vbnRocyAod2l0aCBsb3cgY291bnRzKQogICAgbWVhbigpCmBgYAoKVGhhdCdzIGEgbG90IG9mIGNyaW1lIGluIGEgbW9udGggYW5kIHllYXIuIExldCdzIHNlZSwgSSBndWVzcyB0aGUgYmVzdCB3YXkgdG8gZG8gaXQgaXMgdG8gc2VsZWN0IGEgcGVyaW9kIHRoYXQgd2lsbCBub3QgYmUgZG93bi13ZWlnaHRlZCBhbmQgdGhlbiBncmFkdWFsbHkgZG93bi13ZWlnaHQgYnkgbW9udGggKEkgbWF5IGhhdmUgdG8gaXRlcmF0ZSBvbiB0aGlzKS4gSW4gdGhlIGxhc3Qgc2l6ZSBtb250aHMgdGhlcmUgd291bGQgaGF2ZSBiZWVuIGFib3V0IGByIDYqNDA1NWAgY3JpbWVzLiBUaGUgW2FyZWEgb2YgQmFsdGltb3JlIENpdHldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0JhbHRpbW9yZSkgaXMgYWJvdXQgOTIuMSBzcSBtaWxlcy4gU28gdGhhdCdzIGFib3V0IGByIHJvdW5kKDYqNDA1NS85Mi4xKWAgY3JpbWVzIHBlciBzcXVhcmUgbWlsZSB3aGljaCBkb2Vzbid0IHNlZW0gdG9vIGhlYXZ5LgoKIyMjIEknTSBOT1QgVEFLSU5HIFRISVMgQVBQUk9BQ0gKU28gYW55dGhpbmcgd2l0aGluIHRoZSBsYXN0IDYgbW9udGhzIHdpbGwgYmUgb3VyICRmaW5hbFxfd3QgKiAxJC4gVGhlcmUgYXJlIGByIG5fZGlzdGluY3QocGFzdGUwKHRtcCR5ZWFyLCB0bXAkbW9udGgpKWAgbW9udGhzIGluIHRoZSBkYXRhIHNldCBzbyB3ZSdsbCBqdXN0IGRldmVsb3AgYSBzZXF1ZW5jZSBmcm9tIHRoaXMgbW9udGggdG8gdGhlIGxhc3QgaGVhZGVkIHRvd2FyZCB6ZXJvLgoKIyMjIEdyYWR1YWxseSBkb3dud2VpZ2h0IHN0YXJ0aW5nIGZyb20gdGhpcyBtb250aCBiYWNrd2FyZAoKYGBge3J9CiMgc3BsaXQgaW50byA2MyBwZXJpb2RzIHN0YXJ0aW5nIGF0IHRvZGF5CnQgPC0gbWFwX2RibCgwOjYzLCB+dG9kYXkoKSAlbS0lIG1vbnRocygueCkpICU+JSBhc19kYXRlKCkKCiMgb2xkZXN0IGRhdGUgYmV0d2VlbiBsYXN0IDIgdmFsdWVzPwpudGgodCwgLTIpID4gbWluKGNyaW1lJENyaW1lRGF0ZSkgJiBtaW4oY3JpbWUkQ3JpbWVEYXRlKSA+IGxhc3QodCkKCiMgd2VpZ2h0cyBieSBtb250aAptb250aF93dCA8LSBzZXEoMSwgMCwgYWxvbmcud2l0aCA9IHQpCmBgYAoKbm93IGFkZCB0aGlzIHRvIGBjcmltZWAKCmBgYHtyfQojIFNFVCB0aW1lX3d0IGFzIGRvdWJsZSB3aXRoIGxlbmd0aCA9IGNyaW1lIG9ic2VydmF0aW9ucwp0X3d0IDwtIG51bWVyaWMobnJvdyhjcmltZSkpCgojIEZPUiBlYWNoIG1vbnRoIGRpdmlzaW9uLCBtCiMgICAgSUYgZGF0ZSBpcyA8IHVwcGVyIG1vbnRoIGRhdGUgQU5EIGRhdGUgPj0gbG93ZXIgbW9udGggZGF0ZSBUSEVOCiMgICAgICAgIFNFVCB3ZWlnaHQgZXF1YWwgdG8gd2VpZ2h0IGZvciB0aGlzIGRpdmlzaW9uCiMgICAgRUxTRQojICAgICAgICBETyBub3RoaW5nCiNFTkQgRk9SCgpmb3IgKG4gaW4gc2VxX2Fsb25nKHQpKSB7CiAgICB0X3d0IDwtIGlmX2Vsc2UoCiAgICAgICAgY3JpbWUkQ3JpbWVEYXRlIDwgdFtuXSAmIGNyaW1lJENyaW1lRGF0ZSA+PSB0W24gKyAxXSwKICAgICAgICBtb250aF93dFtuXSwKICAgICAgICB0X3d0CiAgICAgICAgKQp9CgojIHZlcmlmeSBjb3JyZWN0CnVuaXF1ZSh0X3d0KQpsZW5ndGgodF93dCkgPT0gbnJvdyhjcmltZSkKYGBgCgpgYGB7cn0KY3JpbWUgPC0gbXV0YXRlKGNyaW1lLAogICAgICAgICAgICAgICAgdGltZV93dCA9IHRfd3QsCiAgICAgICAgICAgICAgICBvdmVyYWxsX3d0ID0gKGNyaW1lX3d0ICsgd2VwX3d0KSAqIHRpbWVfd3QpCgojIHZlcmlmeSBubyB6ZXJvIHZhbHMKcmFuZ2UoY3JpbWUkb3ZlcmFsbF93dCkKc3RyKGNyaW1lKQpgYGAKCiMgV3JpdGUgdG8gY3N2IGZvciBsb2FkaW5nIHRvIGdvb2dsZSBmdXNpb24gdGFibGVzCgpgYGB7cn0Kd3JpdGVfY3N2KGNyaW1lLCBwYXRoID0gcGFzdGUwKHN1YigiLmNzdiQiLCAiIiwgc2F2ZV9maWxlKSwgIi1XRUlHSFRFRC5jc3YiKSkKYGBgCgoKIyBBdHRlbXB0ZWQgR29vZ2xlIEZ1c2lvbiBUYWJsZQoKQWZ0ZXIgbG9hZGluZyB0aGUgZGF0YSBpbnRvIGEgbmV3IGZ1c2lvbiB0YWJsZSwgSSBzZXQgdGhlIGxvY2F0aW9uIHRvIHVzZSBgbGF0aXR1ZGVgIGFuZCBgbG9uZ2l0dWRlYCwgY2hvc2UgaGVhdCBtYXAsIHNldCB0aGUgd2VpZ2h0cyB0byBgb3ZlcmFsbF93dGAsIGFuZCBhdHRlbXB0ZWQgdmFyaW91cyBwb3NpdGlvbnMgZm9yIHRoZSAicmFkaXVzIi4gVGhlIHJlc3VsdGluZyBtYXAgd2FzIHVuZXhwZWN0ZWRseSBzcGFyc2UuCgo8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlciIgbWFya2Rvd249IjEiPgohWyoqQmFsdGltb3JlIENpdHkgQ3JpbWUgTWFwKipdKEJDX2hlYXRtYXBfbm9GaWx0ZXIuUE5HKXsgd2lkdGg9NTAlIH0KPC9kaXY+CgpJdCB0dXJucyBvdXQgdGhlIGZ1c2lvbiB0YWJsZSBtYXAgaXMgdXNpbmcgdGhlIFtNQVBTIEFQSSBIZWF0bWFwIGxheWVyXShodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9tYXBzL2RvY3VtZW50YXRpb24vamF2YXNjcmlwdC9sYXllcnMjSlNIZWF0TWFwcykgd2hpY2ggY2Fubm90IGhhbmRsZSBtYXBwaW5nIG1vcmUgdGhhbiBbMSwwMDAgcm93c10oaHR0cHM6Ly9zdXBwb3J0Lmdvb2dsZS5jb20vZnVzaW9udGFibGVzL2Fuc3dlci8xMTUyMjYyKS4gTm8gd29uZGVyIHRoZSBkYXRhIHdhcyBzcGFyc2UhCgpBdCB0aGlzIHBvaW50IEkgcmVhbGl6ZWQgdGhlIG1hcCB3YXNuJ3QgZ29pbmcgdG8gYmUgd2hhdCBJIGhvcGVkLiBCdXQsIG91dCBvZiBjdXJpb3NpdHksIEkgYWRqdXN0ZWQgdGhlIG1hcCBieSBmaWx0ZXJpbmcgdGhlIGRhdGEuIEkgd2FzIGFibGUgdG8gcmVkdWNlIHRoZSBudW1iZXIgb2Ygcm93cyB0byA5OTggYnkgc2VsZWN0aW5nIG9ubHkgdGhlIGRhdGEgb2NjdXJyaW5nIGJldHdlZW4gMjAxOC0wMS0wMSBhbmQgMjAxOC0wMy0xMCBhbmQgc2V0dGluZyBhIGZpbHRlciB0byBpbmNsdWRlIG9ubHkgdGhlIHRvcCA2IHdvcnNlIGNyaW1lcyAoaW4gbXkgb3BpbmlvbjogSG9taWNpZGUsIEFnZy4gQXNzYXVsdCwgUmFwZSwgU2hvb3RpbmcsIFJvYmJlcnkgLSBDYXJqYWNraW5nLCBhbmQgUm9iYmVyeSAtIFJlc2lkZW5jZSkuCgo8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlciIgbWFya2Rvd249IjEiPgohWyoqQmFsdGltb3JlIENpdHkgQ3JpbWUgTWFwIC0gRmlsdGVyZWQqKl0oQkNfaGVhdG1hcF93RmlsdGVyLlBORyl7IHdpZHRoPTUwJSB9CjwvZGl2PgoKU2hlZXNoISBFdmVuIHdpdGggb25seSBhYm91dCAyIG1vbnRocyBhbmQgNiB0eXBlcyBvZiBjcmltZSBCYWx0aW1vcmUgQ2l0eSBhdCB0aGlzIGxldmVsIGxvb2tzIGxpa2UgYSBwcmV0dHkgZGFuZ2Vyb3VzIHBsYWNlIChleGNlcHQgbWF5YmUgdGhlIFJvbGFuZCBQYXJrIGFyZWEpLgoKSWYgSSB6b29tIGluIGZ1cnRoZXIgdG8gZWl0aGVyIG9mIHRoZXNlIG1hcHMgdG8gbG9vayBhdCB0aGUgZGF0YSBvbiBhIG5laWdoYm9yaG9vZCBsZXZlbCB0aGUgaW5kaXZpZHVhbCB2YWx1ZXMgYmVjb21lIHRvbyBzcGFyc2UgdG8gYmUgdXNlZnVsLiBBdCB0aGlzIHpvb20gbGV2ZWwsIHRoZSBzYWZlciBsb29raW5nIGFyZWFzIHByb2JhYmx5IGxvb2sgc2FmZXIgYmVjYXVzZSBvZiBtaXNzaW5nIGRhdGEsIG1vcmUgc28gdGhhbiBfYWN0dWFsbHlfIGJlaW5nIHNhZmVyLgoKKipJZiB0aGUgd2hvbGUgcG9pbnQgb2YgdGhpcyBpcyB0byBpZGVudGlmeSBzYWZlciBhcmVhcyBmb3IgYSBmYW1pbHkgdG8gbGl2ZSBpbi4gSSdtIGdvaW5nIHRvIG5lZWQgYSBuZXcgYXBwcm9hY2ghKio=